package com.idi.maven.plugins;

import com.idi.maven.plugins.servers.ServerTypeEnum;
import com.idi.remote.assistant.RemoteAccessFacade;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.maven.plugin.MojoExecutionException;
import org.jfrog.jade.plugins.common.injectable.MvnInjectableSupport;

import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.lang.String.format;

/**
 * Common services for the webstart and ear deployers
 *
 * @author Yossi Shaul
 */
public class DeploymentHelper extends MvnInjectableSupport {
    private int currentVersion = -1;

    private BuildProperties conf;
    private String jndiServer;
    private RemoteAccessFacade remoteAccessFacade;

    //constants
    public static final String IDI_PREFIX = "idi";
    public static final String JBossCluster_dir_prefix = "JBossCluster";


    public DeploymentHelper(BuildProperties conf, String jndiServer) {
        this.conf = conf;
        this.jndiServer = jndiServer;
        this.remoteAccessFacade = new RemoteAccessFacade(getLog(), conf);
    }

    /**
     * Execute SQL select to get the current (production) version from the database
     *
     * @return Current ear version
     */
    public synchronized int getCurrentVersion() {

        if (currentVersion > 0) {
            return currentVersion; // return cached value
        }

        Connection con = null;
        try {
            con = getSqlConnection(false);
            Statement select = con.createStatement();
            ResultSet result = select.executeQuery(
                    "select APP_VERSION from J_APP_VERSION where APP_SERVER = '" + jndiServer + "'");

            if (!result.next()) {
                throw new MojoExecutionException("Select query returned empty result");
            }
            currentVersion = result.getInt(1);
            return currentVersion;

        } catch (Exception e) {
            throw new RuntimeException("Sql query failed with exception", e);
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (Exception e) {
                    getLog().error(e);
                }
            }
        }
    }

    /**
     * get's a sql connection, according to the environment properties
     * you are responsible to close the connection
     *
     * @return sql connection
     */
    @SuppressWarnings({"JDBCResourceOpenedButNotSafelyClosed"})
    public Connection getSqlConnection(boolean userForVersioning) {

        String environment = conf.get("environment");
        getLog().info(format("using database [%s]", environment));

        String suffix = userForVersioning ? ".for.versioning" : "";
        String dbIp = conf.get("db." + environment + ".ip");
        String dbPort = conf.get("db." + environment + ".port");

        String url = "jdbc:sybase:Tds:" + dbIp + ":" + dbPort + "/" + environment;
        String dbUser = conf.get("db." + environment + ".user" + suffix);
        String dbPassword = conf.get("db." + environment + ".password" + suffix);

        try {
            String driver = "com.sybase.jdbc3.jdbc.SybDriver";
            Class.forName(driver).newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Failed loading database driver", e);
        }

        Connection con;
        try {
            con = DriverManager.getConnection(url, dbUser, dbPassword);
        } catch (SQLException e) {
            throw new RuntimeException("failed to get sql connection", e);
        }
        return con;
    }

    public boolean isRemoteFileExist(String dir, String fileName) throws MojoExecutionException {
        FTPClient ftpClient = null;
        try {
            ftpClient = remoteAccessFacade.getFtpClient(conf.getServerHost());
            String[] files = ftpClient.listNames(dir);
            return Arrays.asList(files).contains(fileName);
        } catch (IOException e) {
            throw new MojoExecutionException("Failed connecting to FTP server", e);
        } finally {
            remoteAccessFacade.closeAll();
        }
    }

    /**
     * @param version The current production version
     * @return The name of the current ear name
     */
    public String getCurrentEarName(int version) {
        return IDI_PREFIX + version + ".ear";
    }

    public boolean isClustered() {
        List<String> servers = conf.getServerHosts();
        return servers != null && servers.size() > 1;
    }

    public boolean isTerracottaClustered() {
        return conf.getTerracottaSecondServerHost() != null;
    }

    public boolean isTerracottaProvided() {
        return conf.getTerracottaServerHost() != null;
    }

    public Integer getLocalJndiServerPort() {
        String portString = conf.getLocalJndiServerPort();
        return portString != null ? Integer.valueOf(portString) : 1099; //default is 1099
    }

    /**
     * @return new ear version
     * @throws MojoExecutionException
     */
    public int getNewVersion() {
        return getCurrentVersion() + 1;
    }

    /**
     * return idi ear version on serverHost:dir otherwise null
     *
     * @param serverHost
     * @param dir
     * @return idi ear version on serverHost:dir otherwise null
     * @throws MojoExecutionException
     */
    public Integer getEarVersionOn(final String serverHost, final String dir) throws MojoExecutionException {
        Integer version = null;

        if (dir == null) {
            return null;
        }

        try {
            final FTPClient ftpClient = remoteAccessFacade.getFtpClient(serverHost);
            final FTPFile[] files = ftpClient.listFiles(dir);
            for (final FTPFile file : files) {
                final String filename = file.getName();
                if (filename.startsWith(IDI_PREFIX) && filename.endsWith(".ear")) {
                    version = stripVersion(filename);
                    break;
                }
            }
        } catch (IOException e) {
            throw new MojoExecutionException(format("could not get ear version on %s / %s", serverHost, dir), e);
        } finally {
            remoteAccessFacade.closeAll();
        }
        return version;
    }

    private Integer stripVersion(final String filename) {
        StringBuilder sb = new StringBuilder(3);
        final char[] chars = filename.toCharArray();
        for (final char aChar : chars) {
            if (CharUtils.isAsciiNumeric(aChar)) {
                sb.append(aChar);
            }
        }
        String s = sb.toString();
        return Integer.valueOf(s);
    }

    /**
     * @return The directory on the server holding the current (production) ear file
     * @throws MojoExecutionException If failed to get the current version or to connect to the remote server
     */
    public String getCurrentEarDir() throws MojoExecutionException {
        if (!conf.isVersioningSupported()) {
            return conf.getFirstEarDeployDir();
        }

        // check if the current ear is in the first ear deploy dir
        String currentEarName = getCurrentEarName(getCurrentVersion());
        boolean inFirstDeployDir = isRemoteFileExist(conf.getFirstEarDeployDir(), currentEarName);
        return inFirstDeployDir ? conf.getFirstEarDeployDir() : conf.getSecondEarDeployDir();
    }

    public String getCurrentEarRootDir() throws MojoExecutionException {
        if (!conf.isVersioningSupported()) {
            return conf.getFirstEarRootDir();
        }

        // check if the current ear is in the first ear deploy dir
        boolean inFirstDeployDir = isRemoteFileExist(
                conf.getFirstEarDeployDir(), getCurrentEarName(getCurrentVersion()));
        return inFirstDeployDir ? conf.getFirstEarRootDir() : conf.getSecondEarRootDir();
    }

    /**
     * @return new version ear dir or first dir if not versioned
     * @throws MojoExecutionException
     */
    public String getNewVersionEarDir() throws MojoExecutionException {
        if (!conf.isVersioningSupported()) {
            return conf.getFirstEarDeployDir();
        }

        boolean inFirstDeployDir = isRemoteFileExist(
                conf.getFirstEarDeployDir(), getCurrentEarName(getCurrentVersion()));
        return inFirstDeployDir ? conf.getSecondEarDeployDir() : conf.getFirstEarDeployDir();
    }

    public String getNewVersionEarRootDir() throws MojoExecutionException {
        if (!conf.isVersioningSupported()) {
            return conf.getFirstEarRootDir();
        }

        boolean inFirstDeployDir = isRemoteFileExist(
                conf.getFirstEarDeployDir(), getCurrentEarName(getCurrentVersion()));
        return inFirstDeployDir ? conf.getSecondEarRootDir() : conf.getFirstEarRootDir();
    }

    /**
     * @return The directory to which the new webstart should be copied
     */
    public String getClientRemoteDirectory() {
        if (conf.isVersioningSupported()) {
            return conf.getClientForTestingRemoteDir();
        } else {
            return conf.getClientRemoteDir();
        }
    }

    /**
     * @return list of all server hosts - including clustered hosts if available
     */
    public List<String> getAllServerHosts() {
        return conf.getServerHosts();
    }

    public List<String> getAllTerracottaServerHosts() {
        List<String> result = new ArrayList<String>(2);
        result.add(conf.getTerracottaServerHost());
        if (isTerracottaClustered()) {
            result.add(conf.getTerracottaSecondServerHost());
        }
        return result;
    }

    public List<String> getAllDeployDirs() {
        List<String> allDeployDirs = new ArrayList<String>(2);
        allDeployDirs.add(conf.getFirstEarDeployDir());
        if (conf.isVersioningSupported()) {
            allDeployDirs.add(conf.getSecondEarDeployDir());
        }
        return allDeployDirs;
    }

    public ServerTypeEnum getServerType() {
        return ServerTypeEnum.resloveServerType(conf.getServerType());
    }
}
